/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections;
using System.Windows;
using Xceed.Silverlight.Utils;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace Xceed.Silverlight.Controls
{
  public class RangeDataNavigationConfiguration : DataNavigationConfiguration
  {
    #region Start Property

    public static readonly DependencyProperty StartProperty = DependencyProperty.Register(
      "Start",
      typeof( object ),
      typeof( RangeDataNavigationConfiguration ),
      new PropertyMetadata( null, 
        new PropertyChangedCallback( RangeDataNavigationConfiguration.OnRangeRelatedPropertyChanged ) ) );

    public object Start
    {
      get
      {
        return this.GetValue( RangeDataNavigationConfiguration.StartProperty );
      }
      set
      {
        this.SetValue( RangeDataNavigationConfiguration.StartProperty, value );
      }
    }

    #endregion

    #region End Property

    public static readonly DependencyProperty EndProperty = DependencyProperty.Register(
      "End",
      typeof( object ),
      typeof( RangeDataNavigationConfiguration ),
      new PropertyMetadata(
        null,
        new PropertyChangedCallback( RangeDataNavigationConfiguration.OnRangeRelatedPropertyChanged ) ) );

    public object End
    {
      get
      {
        return this.GetValue( RangeDataNavigationConfiguration.EndProperty );
      }
      set
      {
        this.SetValue( RangeDataNavigationConfiguration.EndProperty, value );
      }
    }

    #endregion

    #region Step Property

    public static readonly DependencyProperty StepProperty = DependencyProperty.Register(
      "Step",
      typeof( object ),
      typeof( RangeDataNavigationConfiguration ),
      new PropertyMetadata( null,
        new PropertyChangedCallback( RangeDataNavigationConfiguration.OnRangeRelatedPropertyChanged ) ) );

    public object Step
    {
      get
      {
        return this.GetValue( RangeDataNavigationConfiguration.StepProperty );
      }
      set
      {
        this.SetValue( RangeDataNavigationConfiguration.StepProperty, value );
      }
    }

    #endregion

    public override IList GetKeys()
    {
      return new ReadOnlyObservableCollection<object>( m_keys );
    }

    protected virtual void UpdateKeys()
    {
#if !WINDOWS_PHONE
      if( !this.IsInitialized )
        return;
#endif

      double calculatedStart;
      double calculatedEnd;
      double calculatedStep;
      Type keyDataType = this.ExtractDataTypeAndValues( out calculatedStart, out calculatedEnd, out calculatedStep );

      using( m_keys.DeferPropertyAndCollectionChanged() )
      {
        m_keys.Clear();

        if( calculatedStart < calculatedEnd )
        {
          for( double i = calculatedStart; i <= calculatedEnd; i += calculatedStep )
          {
            m_keys.Add( System.Convert.ChangeType( i, keyDataType, System.Globalization.NumberFormatInfo.InvariantInfo ) );
          }
        }
        else if( calculatedEnd < calculatedStart )
        {
          for( double i = calculatedStart; i >= calculatedEnd; i -= calculatedStep )
          {
            m_keys.Add( System.Convert.ChangeType( i, keyDataType, System.Globalization.NumberFormatInfo.InvariantInfo ) );
          }
        }
        else
        {
          m_keys.Add( System.Convert.ChangeType( calculatedStart, keyDataType, System.Globalization.NumberFormatInfo.InvariantInfo ) );
        }
      }
    }

    private Type ExtractDataTypeAndValues( out double calculatedStart, out double calculatedEnd, out double calculatedStep )
    {
      object start = this.Start;
      object end = this.End;
      object step = this.Step;

      if( ( start == null ) && ( end == null ) )
      {
        throw new InvalidOperationException( "The start and end values of the RangeDataNavigationConfiguration must be specified." );
      }

      Type keyDataType = ( start != null ) ? start.GetType() : end.GetType();

      if( start == null )
      {
        start = System.Convert.ChangeType( 0.0d, keyDataType, System.Globalization.NumberFormatInfo.InvariantInfo );
      }
      else if( start is string )
      {
        throw new InvalidOperationException( "The start value of the RangeDataNavigationConfiguration cannot be a string. If set in XAML, the data type must be explicitly defined." );
      }
      else if( !( start is ValueType ) )
      {
        throw new InvalidOperationException( "The start value of the RangeDataNavigationConfiguration must be a value type." );
      }

      if( end == null )
      {
        end = System.Convert.ChangeType( 0.0d, keyDataType, System.Globalization.NumberFormatInfo.InvariantInfo );
      }
      else if( end is string )
      {
        throw new InvalidOperationException( "The end value of the RangeDataNavigationConfiguration cannot be a string. If set in XAML, the data type must be explicitly defined." );
      }
      else if( !( end is ValueType ) )
      {
        throw new InvalidOperationException( "The end value of the RangeDataNavigationConfiguration must be a value type." );
      }

      calculatedStart = System.Convert.ToDouble( start );
      calculatedEnd = System.Convert.ToDouble( end );

      if( step == null )
      {
        if( calculatedStart != calculatedEnd )
        {
          step = System.Convert.ChangeType( 1.0d, keyDataType, System.Globalization.NumberFormatInfo.InvariantInfo );
        }
        else
        {
          step = System.Convert.ChangeType( 0.0d, keyDataType, System.Globalization.NumberFormatInfo.InvariantInfo );
        }
      }
      else if( step is string )
      {
        throw new InvalidOperationException( "The step value of the RangeDataNavigationConfiguration cannot be a string. If set in XAML, the data type must be explicitly defined." );
      }
      else if( !( step is ValueType ) )
      {
        throw new InvalidOperationException( "The step value of the RangeDataNavigationConfiguration must be a value type." );
      }


      if( ( start.GetType() != end.GetType() ) || ( start.GetType() != step.GetType() ) )
      {
        throw new InvalidOperationException( "The start, end, and step values of the RangeDataNavigationConfiguration must be of the same data type." );
      }

      calculatedStep = Math.Abs( System.Convert.ToDouble( step ) );

      return keyDataType;
    }

    protected override void EndInitCore()
    {
      base.EndInitCore();
      this.UpdateKeys();
    }

    private static void OnRangeRelatedPropertyChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      RangeDataNavigationConfiguration configuration = sender as RangeDataNavigationConfiguration;

      if( configuration == null )
        return;

      configuration.UpdateKeys();
    }

    private DeferableObservableCollection<object> m_keys = new DeferableObservableCollection<object>();
  }
}
